package ru.nsu.fit.client.core;

import ru.nsu.fit.client.core.commands.*;
import ru.nsu.fit.client.core.log_manager.realization.KernelLogManager;
import ru.nsu.fit.client.core.response.KernelResponse;
import ru.nsu.fit.client.core.soap.server_info.ServerInfo;
import ru.nsu.fit.client.core.soap.server_info.ServerInfoOutput;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ClientKernel implements  IClientKernel {
    enum ClientState
    {
        StateNotInitialized,
        StateOk,
        StateInvalidServer,
        StateServerNotRespond
    };
    private static final int MAX_THREADS = 3;

    private int lastId = 0;
    private long userSessionId = 0;
    private final Map<Integer, KernelResponse> completeResponses = new HashMap<Integer, KernelResponse>();
    private static ExecutorService executor = new ThreadPoolExecutor(1, MAX_THREADS, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(MAX_THREADS, true), new ThreadPoolExecutor.CallerRunsPolicy());
    private static int instancesCreated = 0;
    private KernelConfig config;
    private ClientState state = ClientState.StateNotInitialized;
    private int lastQuestion = -1;

    public ClientKernel()
    {
        synchronized (executor)
        {
            instancesCreated++;
        }
        config = KernelConfig.getDefault();
    }

    @Override
    public void finalize() throws Throwable {
        synchronized (executor)
        {
            instancesCreated--;
            if (instancesCreated == 0)
            {
                executor.shutdown();
            }
        }

        super.finalize();
    }

    public ClientKernel(KernelConfig config)
    {
        this.config = config;
    }

    @Override
    public int registration(String login, String password) {
        final int id = getSafeId();

        execCommand(id, new RegistrationCommand(config.getServerUrl(), login, password));

       return id;
    }

    @Override
    public int authentication(String login, String password) {
        final int id = getSafeId();

        execCommand(id, new AuthenticationCommand(config.getServerUrl(), login, password));

        return id;
    }

    @Override
    public int sendQuestion(String text, List<String> cases) {
        final int id = getSafeId();

        execCommand(id, new SendQuestionCommand(config.getServerUrl(), userSessionId, text, cases));

        return id;
    }

    @Override
    public int sendAnswer(int questionId, int caseId) {
        final int id = getSafeId();

        execCommand(id, new SendAnswerCommand(config.getServerUrl(), userSessionId, questionId, caseId));
        return id;
    }

    @Override
    public int getUserStatistics() {
        final int id = getSafeId();

        execCommand(id, new GetUserStatCommand(config.getServerUrl(), userSessionId));

        return id;
    }

    @Override
    public int getNextQuestion() {
        final int id = getSafeId();

        execCommand(id, new GetQuestionsCommand(config.getServerUrl(), userSessionId, 1, ++lastQuestion, 0, 0, 0));

        return id;
    }

    @Override
    public int getQuestions(int count, int offset) {
        final int id = getSafeId();

        execCommand(id, new GetQuestionsCommand(config.getServerUrl(), userSessionId, count, offset, 0, 0, 0));

        return id;
    }

    @Override
    public int getQuestionsStats(List<Integer> ids) {
        final int id = getSafeId();

        execCommand(id, new GetQuestionsStatCommand(config.getServerUrl(), userSessionId, ids));

        return id;
    }

    @Override
    public KernelResponse getResponse(int id) {
        synchronized (completeResponses)
        {
            if (completeResponses.containsKey(id))
            {
                KernelResponse response = completeResponses.get(id);
                completeResponses.remove(id);
                return response;
            }
            else
            {
                return null;
            }
        }
    }

    private synchronized  int getSafeId()
    {
        lastId = (lastId + 1) % (1 << 30);
        return lastId;
    }

    private void generateDefaultErrorResponse(int id)
    {
        KernelResponse response = new KernelResponse(false);
        switch (state)
        {
            case StateInvalidServer:
                response.setErrorText("Unsupported server version");
                break;
            case StateServerNotRespond:
                response.setErrorText("Cannot connect to server");
                break;
            default:
            	break;
        }

        response.setId(id);

        synchronized (completeResponses)
        {
            completeResponses.put(id, response);
        }
    }

    private boolean initCommand()
    {
        if (state.equals(ClientState.StateNotInitialized) || state.equals(ClientState.StateServerNotRespond))
        {
            try
            {
                KernelLogManager.print("Fetching server data...");
                ServerInfoOutput output = ServerInfo.execute(config.getServerUrl());
                KernelLogManager.printf("Success! Server: %s Version: %s Date: %s\n", output.getInfo(), output.getVersion(), output.getDate());

                if (!config.isVersionSupported(output.getVersion()))
                {
                    state = ClientState.StateInvalidServer;
                    KernelLogManager.printf("Version %s is not supported!\n", output.getVersion());
                }
                else
                {
                    state =  ClientState.StateOk;
                }
            }
            catch (Exception e)
            {
                KernelLogManager.printf("Cannot initialize service: %s\n", e.getMessage());
                state = ClientState.StateServerNotRespond;
            }
        }

        return (state.equals(ClientState.StateOk));
    }

    private  void execCommand(final int id, final IClientCommand command)
    {
        executor.submit(new Runnable() {
            @Override
            public void run() {
                if (!initCommand()) {
                    generateDefaultErrorResponse(id);
                    return;
                }

                KernelResponse response = command.execute();
                response.setId(id);

                synchronized (completeResponses) {
                    if (response.isChangeUserSessionId())
                    {
                        userSessionId = response.getUserSessionId();
                        lastQuestion = -1;
                    }
                    completeResponses.put(id, response);
                }
            }
        });
    }
}
